package be.pluyckx.batteryprogress.components.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

import be.pluyckx.batteryprogress.R;
import be.pluyckx.batteryprogress.data.history.BatteryLevel;

/**
 * Created by Philip on 21/08/13.
 */
public class Chart extends View {
  private static final Handler mThreadHandler = new Handler();

  private DataCollection mData = null;
  private Paint mPointStyleDischarge;
  private Paint mLineStyleDischarge;
  private Paint mPointStyleCharge;
  private Paint mLineStyleCharge;
  private Paint mAxesStyle;
  private Paint mText;

  private float mLineWidth = 1;
  private float mAxesWidth = 1;
  private float mPointRadius = mLineWidth * 2;

  private float mAxesOffsetX = 5.0f;
  private float mAxesOffsetY = 5.0f;
  private float mAxesTextSpace = 1.0f;
  private float mOffsetMaxY = 0.0f;
  private float mOffsetOrigin = 0.0f;

  private Rect mMaxXTextBounds = new Rect();
  private Rect mMaxYTextBounds = new Rect();
  private Rect mOriginTextBounds = new Rect();

  private DataItem mBuffItem;

  private DataListener mDataListener = new DataListener();

  public Chart(Context context) {
    super(context);

    init(null);
  }

  public Chart(Context context, AttributeSet attrs) {
    super(context, attrs);

    init(attrs);
  }

  public Chart(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    init(attrs);
  }

  private void init(AttributeSet attrs) {
    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Chart, 0, 0);

    mLineWidth = 2 * getResources().getDisplayMetrics().density;
    mPointRadius = mLineWidth * 2;

    try {
      mLineWidth = a.getFloat(R.styleable.Chart_lineWidth, mLineWidth);
      mPointRadius = a.getFloat(R.styleable.Chart_pointRadius, mPointRadius);
    } finally {
      a.recycle();
    }

    mPointStyleDischarge = new Paint();
    mPointStyleDischarge.setColor(Color.RED);
    mPointStyleDischarge.setStyle(Paint.Style.FILL);

    mLineStyleDischarge = new Paint();
    mLineStyleDischarge.setColor(Color.RED);
    mLineStyleDischarge.setStyle(Paint.Style.STROKE);
    mLineStyleDischarge.setStrokeWidth(mLineWidth);

    mPointStyleCharge = new Paint(mPointStyleDischarge);
    mPointStyleCharge.setColor(Color.GREEN);
    mLineStyleCharge = new Paint(mLineStyleDischarge);
    mLineStyleCharge.setColor(Color.GREEN);

    mAxesStyle = new Paint();
    mAxesWidth = getResources().getDisplayMetrics().density * 2;
    mAxesStyle.setColor(Color.LTGRAY);
    mAxesStyle.setStyle(Paint.Style.STROKE);
    mAxesStyle.setStrokeWidth(mAxesWidth);

    mAxesTextSpace = getResources().getDisplayMetrics().density * 5.0f;
    mAxesOffsetX = getResources().getDisplayMetrics().density * 5.0f + mAxesTextSpace;
    mAxesOffsetY = getResources().getDisplayMetrics().density * 5.0f + mAxesTextSpace;

    mText = new Paint();
    mText.setTextSize(getResources().getDisplayMetrics().scaledDensity * 12);
    mText.setColor(Color.BLACK);
  }

  @Override
  protected void onDetachedFromWindow() {
    if (mData != null) {
      mData.removeListener(mDataListener);
    }

    super.onDetachedFromWindow();
  }

  public void setData(DataCollection data) {
    if (mData == data) {
      return;
    }

    if (mData != null) {
      mData.removeListener(mDataListener);
    }

    mData = data;
    if (mData != null) {
      mData.addListener(mDataListener);
      mBuffItem = data.createItem();

      calculateOffsets();
    }
    invalidate();
  }

  private void calculateOffsets() {
    mText.getTextBounds(mData.getMaxYAsString(), 0, mData.getMaxYAsString().length(), mMaxYTextBounds);
    mAxesOffsetX = mMaxYTextBounds.width() + mAxesTextSpace + getPaddingLeft();

    mText.getTextBounds(mData.getMaxXAsString(), 0, mData.getMaxXAsString().length(), mMaxXTextBounds);
    mAxesOffsetY = mMaxXTextBounds.height() + mAxesTextSpace + getPaddingBottom();

    mText.getTextBounds("0", 0, 1, mOriginTextBounds);

    if (mMaxYTextBounds.width() > mOriginTextBounds.width()) {
      mOffsetOrigin = mMaxYTextBounds.width() - mOriginTextBounds.width();
      mOffsetMaxY = 0;
    } else {
      mOffsetMaxY = mOriginTextBounds.width() - mMaxYTextBounds.width();
      mOffsetOrigin = 0;
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    float paddingLeft = getPaddingLeft();
    float paddingBottom = getPaddingBottom();
    float paddingRight = getPaddingRight();
    float paddingTop = getPaddingTop();

    float displayHeight = getHeight();
    float displayWidth = getWidth();
    float axesWidth = displayWidth - mAxesOffsetX - paddingRight;
    float axesHeight = displayHeight - mAxesOffsetY - paddingTop;

    canvas.save();

    // draw the axes
    canvas.drawLine(mAxesOffsetX, paddingTop, mAxesOffsetX, displayHeight - mAxesOffsetY, mAxesStyle);
    canvas.drawLine(mAxesOffsetX, displayHeight - mAxesOffsetY, displayWidth - paddingRight, displayHeight - mAxesOffsetY, mAxesStyle);

    // only draw if there is data
    if (mData != null && mData.size() > 0) {
      mText.setTextAlign(Paint.Align.LEFT);
      canvas.drawText(mData.getMaxYAsString(), getPaddingLeft() + mOffsetMaxY, paddingTop + mMaxYTextBounds.height(), mText);
      canvas.drawText("0", paddingLeft + mOffsetOrigin, displayHeight - paddingBottom, mText);
      mText.setTextAlign(Paint.Align.RIGHT);
      canvas.drawText(mData.getMaxXAsString(), displayWidth - paddingRight, displayHeight - paddingBottom, mText);

      // declare some variables used to draw everything
      float timeDiff = Math.abs(mData.getFirst(mBuffItem).getX() - mData.getLast(mBuffItem).getX());
      float offset = mData.getFirst(mBuffItem).getX();
      float curItemX, curItemY, curDisplayX, curDisplayY;
      float prevItemX, prevItemY, prevDislpayX, prevDisplayY;
      int prevState = -1;
      float maxY = mData.getMaxY();
      float stepY, stepX;
      if (maxY == 0) {
        stepY = 1.0f;
      } else {
        stepY = axesHeight / maxY;
      }
      if (timeDiff == 0) {
        stepX = 1.0f;
      } else {
        stepX = axesWidth / timeDiff;
      }

      int index = 0;
      mData.getFirst(mBuffItem);
      prevItemX = offset;
      prevItemY = mBuffItem.getY();
      prevDislpayX = (prevItemX - offset) * stepX + mAxesOffsetX;
      prevDisplayY = (axesHeight - prevItemY * stepY) + paddingTop;

      do {
        curItemX = mBuffItem.getX();
        curItemY = mBuffItem.getY();
        curDisplayX = (curItemX - offset) * stepX + mAxesOffsetX;
        curDisplayY = (axesHeight - curItemY * stepY) + paddingTop;

        if (mBuffItem.getState() == DataItem.STATE_DISCHARGING) {
          canvas.drawCircle(curDisplayX, curDisplayY, mPointRadius, mPointStyleDischarge);
        } else {
          canvas.drawCircle(curDisplayX, curDisplayY, mPointRadius, mPointStyleCharge);
        }

        if (prevState == mBuffItem.getState()) {
          if (curDisplayX != prevDislpayX || curDisplayY != prevDisplayY) {
            if (mBuffItem.getState() == DataItem.STATE_DISCHARGING) {
              canvas.drawLine(prevDislpayX, prevDisplayY, curDisplayX, curDisplayY, mLineStyleDischarge);
            } else {
              canvas.drawLine(prevDislpayX, prevDisplayY, curDisplayX, curDisplayY, mLineStyleCharge);
            }
          }
        }

        index++;
        if (index < mData.size()) {
          prevState = mBuffItem.getState();
          mData.get(index, mBuffItem);

          prevDislpayX = curDisplayX;
          prevDisplayY = curDisplayY;
        }
      } while (index < mData.size());
    }

    canvas.restore();
  }

  public class DataListener implements DataCollection.DataListener {
    @Override
    public void onDataChanged(DataCollection collection) {
      calculateOffsets();
      mThreadHandler.post(new Runnable() {
        @Override
        public void run() {
          invalidate();
        }
      });
    }
  }

  public static interface DataItem {
    public static final int STATE_CHARGING = BatteryLevel.STATE_CHARGING;
    public static final int STATE_DISCHARGING = BatteryLevel.STATE_DISCHARGING;

    public float getX();

    public float getY();

    public void setX(float x);

    public void setY(float y);

    public int getState();

    public String getXAsString();

    public String getYAsString();
  }

  public static abstract class DataCollection {
    public static final int ORDER_OLD_LOWEST_INDEX = 0;
    public static final int ORDER_NEW_LOWEST_INDEX = 0;

    private List<DataListener> mListeners = new ArrayList<DataListener>();

    public void addListener(DataListener listener) {
      mListeners.add(listener);
    }

    public void removeListener(DataListener listener) {
      mListeners.remove(listener);
    }

    protected void fireDataChanged() {
      for (DataListener l : mListeners) {
        l.onDataChanged(this);
      }
    }

    public abstract int size();

    public abstract DataItem get(int index, DataItem item);

    public abstract DataItem getFirst(DataItem item);

    public abstract DataItem getLast(DataItem item);

    public abstract int getOrdered();

    public abstract float getMaxY();

    public abstract float getMaxX();

    public abstract String getMaxYAsString();

    public abstract String getMaxXAsString();

    public abstract DataItem createItem();

    public static interface DataListener {
      void onDataChanged(DataCollection collection);
    }
  }
}
